﻿namespace LogicStudio.Framework.UI.Comun
{
    using Comandos;
    using Controles;
    using System;
    using System.ComponentModel;
    using System.ServiceModel.DomainServices.Client;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using Utilidades;

    /// <summary>
    /// Clase abstracta generica para la construcción de view-models (vista modelos)
    /// </summary>
    /// <typeparam name="TContexto">
    /// Parámetro del tipo DomainContext (Servicio RIA)
    /// Que se utilizara como fuente de datos
    /// </typeparam>
    /// <typeparam name="TEntidad">
    /// Tipo de la entidad predominante o principal
    /// </typeparam>
    /// <typeparam name="TEditor">
    /// Tipo del editor a utilizarce en el mantenimiento
    /// </typeparam>
    public abstract class ModeloBase<TContexto, TEntidad, TEditor> : ModeloFramework, IObjetoModelo<TContexto, TEntidad>
        where TContexto : DomainContext, new()
        where TEntidad : Entity, new()
        where TEditor : ChildWindow, new()
    {
        #region Atributos

        private Action _cerrarEditor;
        private bool _soloLectura;
        private ControladorConsultaAsincrona _invocadorConsultaAsincrona;
        private ComandoEditorGenerico<TContexto, TEntidad, TEditor> _comandoMantenimiento;
        private DomainDataSource _fuenteDatos;
        private ICommand _comandoAccion;
        private ICommand _comandoDetalle;
        private ModoEntidad _modo;
        private TContexto _contexto;
        private TEntidad _itemSeleccionado;

        #endregion

        #region Constructores

        /// <summary>
        /// Inicializacion de la instancia de la clase
        /// </summary>
        protected ModeloBase()
        {
            HabilitadoModificacion = true;
            EstaProcesando = true;
        }

        #endregion

        #region Constantes

        private const int CantidadPaginacionDefecto = 25;
        private const string CantidadFilasPaginacion = "CantidadPaginacion";
        private const string Eliminacion = "Eliminacion";
        private const string PropiedadContexto = "Contexto";
        private const string PropiedadSoloLectura = "SoloLectura";
        public const string Nombre = "Nombre";
        public const string PropiedadItemSeleccionado = "ItemSeleccionado";
        public const string Valor = "Valor";

        #endregion

        #region Propiedades

        /// <summary>
        /// Propiedad que indica si el modelo ha cambiado de estado
        /// </summary>
        public bool EstaModificado { get; set; }

        /// <summary>
        /// Propiedad auxiliar que presenta unicamente la opcion de seteo<br />
        /// Al colocal el valor a la propiedad, esta invoca a la función privada de ValidarAccion
        /// para la validación del proceso de aceptación o rechazo de cambios en el contexto actual
        /// </summary>
        public bool AplicarGrabado
        {
            set
            {
                ValidarAccion(value);
            }
        }

        /// <summary>
        /// Propiedad que indica si se encuentra habilitado para edición
        /// </summary>
        public bool HabilitadoModificacion
        {
            get
            {
                return _soloLectura;
            }
            set
            {
                if (_soloLectura == value)
                {
                    return;
                }

                _soloLectura = value;
                RaisePropertyChanged(PropiedadSoloLectura);
            }
        }

        /// <summary>
        /// Propiedad del controlador de consulta asincrona
        /// </summary>
        public ControladorConsultaAsincrona InvocadorConsultaAsincrona
        {
            get { return _invocadorConsultaAsincrona ?? (_invocadorConsultaAsincrona = new ControladorConsultaAsincrona()); }
        }

        /// <summary>
        /// Propiedad auxiliar para setear el modo o estado del item seleccionado<br />
        /// En la opcion de seteo invoca a una función privada InvocarModoAccion que es la encargada
        /// de colocar el estado a la entidad en curso
        /// </summary>
        public ModoEntidad Modo
        {
            get
            {
                return _modo;
            }
            set
            {
                _modo = value;
                InvocarModoAccion(value);
            }
        }

        /// <summary>
        /// Propiedad que controla el acceso a la fuente de datos (Contexto del tipo generico)
        /// </summary>
        public DomainDataSource FuenteDatos
        {
            get
            {
                if (_fuenteDatos == null)
                {
                    _fuenteDatos = new DomainDataSource
                    {
                        DomainContext = Contexto,
                        PageSize = CantidadPaginacion,
                        QueryName = NombreMetodoConsulta
                    };
                    _fuenteDatos.LoadedData += CargaDatosCompleta;
                    _fuenteDatos.SubmittedChanges += ClientDataSubmittedChanges;
                }

                return _fuenteDatos;
            }
        }

        /// <summary>
        /// Comando genérico para la manipulación de una entidad seleccionada
        /// </summary>
        public ComandoEditorGenerico<TContexto, TEntidad, TEditor> Mantenimiento
        {
            get { return _comandoMantenimiento ?? (_comandoMantenimiento = new ComandoEditorGenerico<TContexto, TEntidad, TEditor>(this)); }
        }

        /// <summary>
        /// Comando utilizado para invocar a la accion de aceptar los cambios realizados
        /// </summary>
        public ICommand AccionGrabar
        {
            get { return _comandoAccion ?? (_comandoAccion = new ComandoEditor<TContexto, TEntidad>(this)); }
        }

        /// <summary>
        /// Comando utilizado para invocar a la accion de detalle
        /// </summary>
        public ICommand AccionDetalle
        {
            get { return _comandoDetalle ?? (_comandoDetalle = new ComandoDetalle<TContexto, TEntidad>(this)); }
        }

        /// <summary>
        /// Propiedad que retorna la cantidad de filas a desplegarse
        /// </summary>
        protected virtual int CantidadPaginacion
        {
            get
            {
                var filasPagina = 0;
                if (Application.Current.Resources.Contains(CantidadFilasPaginacion))
                {
                    int.TryParse(Application.Current.Resources[CantidadFilasPaginacion].ToString(), out filasPagina);

                    if (filasPagina == 0)
                    {
                        filasPagina = CantidadPaginacionDefecto;
                    }
                }
                return filasPagina;
            }
        }

        /// <summary>
        /// Propiedad que retorna el nombre de la entidad
        /// </summary>
        public virtual string NombreEntidad
        {
            get
            {
                return typeof(TEntidad).Name;
            }
        }

        /// <summary>
        /// Propiedad que retorna el item seleccionado
        /// </summary>
        public virtual TEntidad ItemSeleccionado
        {
            get
            {
                if (Modo == ModoEntidad.Nuevo)
                {
                    return _itemSeleccionado;
                }

                return (TEntidad)FuenteDatos.DataView.CurrentItem;
            }
            set
            {
                if (_itemSeleccionado == value)
                {
                    return;
                }

                _itemSeleccionado = value;
                RaisePropertyChanged(PropiedadItemSeleccionado);
            }
        }

        /// <summary>
        /// Instancia de contecto correspondiente al parámetro genérico
        /// </summary>
        public TContexto Contexto
        {
            get
            {
                if (_contexto == null)
                {
                    _contexto = new TContexto();
                }
                RaisePropertyChanged(PropiedadContexto);
                return _contexto;
            }
        }

        #endregion

        #region Metodos Privados

        /// <summary>
        /// Método que se ejecuta como CallBack al invocar al método SubmitChanges de la Fuente de Datos
        /// </summary>
        /// <param name="sender">Propiedad global FuenteDatos</param>
        /// <param name="e">Argumentos presentados en la aplicación de los cambios</param>
        private void ClientDataSubmittedChanges(object sender, SubmittedChangesEventArgs e)
        {
            if (e.HasError)
            {
                var recurso = RecursosLenguaje.Recursos;
                var mensaje = string.Empty;
                mensaje = recurso.GetType().GetProperty("ErrorEnvioCambios").GetValue(mensaje, null).ToString();
                VentanaError.Crear(mensaje, e.Error);
                e.MarkErrorAsHandled();
            }
            else
            {
                FuenteDatos.Load();
            }
        }

        /// <summary>
        /// Valida el tipo de acción para el Modo de la entidad seleccionada
        /// </summary>
        /// <param name="modoEntidad">Enumerado que indica el modo de la entidad</param>
        private void InvocarModoAccion(ModoEntidad modoEntidad)
        {
            HabilitadoModificacion = true;

            // Si el modo es Nuevo, se crea una entidad, se la agrega al contexto y a la vez se
            // setea dicha entidad como seleccionada para la manipulación de su informacion
            if (modoEntidad == ModoEntidad.Nuevo)
            {
                Agregar();
            }
            // Si el modo es Editar
            else if (modoEntidad == ModoEntidad.Editar)
            {
                AccionItemSeleccionado();
            }
            // Si el modo de la accion es eliminar se invoca al cuadro de dialogo de eliminación
            else if (modoEntidad == ModoEntidad.Eliminar)
            {
                ValidarEliminacion();
            }
            else if (modoEntidad == ModoEntidad.SoloLectura)
            {
                AccionItemSeleccionado();
                HabilitadoModificacion = false;
            }
        }

        /// <summary>
        /// Valida si se aplica o no los cambios realizados al contexto actual
        /// </summary>
        /// <param name="resultadoDialogo">
        /// Dato binario que indica si el usuario desea o no aplicar los cambios, si se indica que si
        /// se desea grabar los cambios la entidad actual es sometida a un proceso de validación de datos
        /// </param>
        private void ValidarAccion(bool resultadoDialogo)
        {
            EstaProcesando = true;
            var aceptar = Mantenimiento.OcultarEditor(resultadoDialogo, out _cerrarEditor);

            if (aceptar == null)
            {
                EstaProcesando = false;
                return;
            }

            if (aceptar.Value)
            {
                (ItemSeleccionado as IEditableObject).EndEdit();
                Contexto.SubmitChanges(EnvioCompleto, null);
            }
            else
            {
                Contexto.RejectChanges();
                EstaProcesando = false;
            }
        }

        /// <summary>
        /// Método privado que valida la eliminación de un item
        /// </summary>
        private void ValidarEliminacion()
        {
            var recurso = RecursosLenguaje.Recursos;
            var confirmacion = recurso.GetType().GetProperty("ConfirmacionEliminacion").GetValue(null, null).ToString();
            var eliminar = recurso.GetType().GetProperty("TituloEliminar").GetValue(null, null).ToString();
            CuadroDialogo.Mostrar(confirmacion, eliminar, TipoVentana.Pregunta, AplicarEliminacion);
        }

        private void AplicarEliminacion()
        {
            Eliminar();
            EstaProcesando = true;
            Contexto.SubmitChanges(EnvioCompleto, Eliminacion);
        }

        #endregion

        #region Metodos Protegidos

        /// <summary>
        /// Método abstracto en el que se implementa el codigo para la agregación de un nuevo item
        /// </summary>
        protected virtual void Agregar()
        {
            if (ItemSeleccionado != null)
            {
                ItemSeleccionado.ValidationErrors.Clear();
            }
        }

        /// <summary>
        /// Propiedad que retorna el nombre del metodo de la consulta a aplicarse a la fuente de datos
        /// </summary>
        protected abstract string NombreMetodoConsulta { get; }

        /// <summary>
        /// Método de eliminación de un item
        /// </summary>
        protected abstract void Eliminar();

        /// <summary>
        /// Método de validación de una entidad
        /// </summary>
        /// <param name="entity">Entidad a validarse</param>
        /// <returns>Los resultados de la validación</returns>
        protected EntityValidationResult ValidateEntity(Entity entity)
        {
            return new EntityValidator().Validate(entity);
        }

        /// <summary>
        /// Método auxiliar en el que se implementará acciones cuando se selecione un item para su modificación
        /// </summary>
        protected virtual void AccionItemSeleccionado()
        {
        }

        /// <summary>
        /// Método de llamado asincrono al completarse el envio de cambios del contexto
        /// </summary>
        /// <param name="operacionEnvio">Resultado de la operación</param>
        protected virtual void EnvioCompleto(SubmitOperation operacionEnvio)
        {
            EstaProcesando = false;

            if (operacionEnvio.HasError)
            {
                var recurso = RecursosLenguaje.Recursos;
                var titulo = recurso.GetType().GetProperty("ErrorEnvioCambios").GetValue(null, null).ToString();

                var error = operacionEnvio.Error.Message;
                var mensaje = error;
                if (error.Contains("duplicate key"))
                {
                    mensaje =
                        recurso.GetType().GetProperty("MensajeClavePrimariaDuplicada").GetValue(null, null).ToString();
                }
                else if (error.Contains("The DELETE statement conflicted with the REFERENCE constraint"))
                {
                    Contexto.RejectChanges();
                    mensaje =
                        recurso.GetType().GetProperty("MensajeRegistroRelacionado").GetValue(null, null).ToString();
                }
                if (Eliminacion.Equals(operacionEnvio.UserState))
                {
                    Contexto.RejectChanges();
                }

                if (!error.Contains("Submit operation failed validation. Please inspect Entity.ValidationErrors for each entity in EntitiesInError for more information."))
                {
                    CuadroDialogo.Mostrar(mensaje, titulo, TipoVentana.Error);
                }
                operacionEnvio.MarkErrorAsHandled();

            }
            else
            {
                if (_cerrarEditor != null)
                {
                    _cerrarEditor();
                }
                EstaProcesando = true;
                FuenteDatos.Load();
            }
        }

        /// <summary>
        /// Método privado estático que se ejecuta como CallBack al invocar al método Load de la Fuente de Datos
        /// </summary>
        /// <param name="sender">Propiedad global FuenteDatos</param>
        /// <param name="e">Argumentos presentados en la ejecución de la carga de datos</param>
        protected virtual void CargaDatosCompleta(object sender, LoadedDataEventArgs e)
        {
            EstaProcesando = false;

            if (!e.HasError)
            {
                return;
            }

            var recurso = RecursosLenguaje.Recursos;
            var mensaje = string.Empty;
            mensaje = recurso.GetType().GetProperty("ErrorCarga").GetValue(mensaje, null).ToString();
            VentanaError.Crear(mensaje, e.Error);
            e.MarkErrorAsHandled();
        }

        #endregion

        #region Metodos Publicos

        /// <summary>
        /// Función para setear valores a un object generado com instancia mediante relection
        /// </summary>
        /// <param name="objeto">Objeto en el que se seteara el valor</param>
        /// <param name="nombrePropiedad">Nombre del campo o propiedad a setearse</param>
        /// <param name="valor">Valor a setearse a la Propiedad</param>
        public static void SetearValorPropiedad(object objeto, string nombrePropiedad, object valor)
        {
            try
            {
                if (string.IsNullOrEmpty(nombrePropiedad))
                {
                    return;
                }

                var property = objeto.GetType().GetProperty(nombrePropiedad);

                if (property == null)
                {
                    return;
                }

                valor = Convert.ChangeType(valor, property.PropertyType, null);
                property.SetValue(objeto, valor, null);
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch { }
            // ReSharper restore EmptyGeneralCatchClause
        }

        /// <summary>
        /// Método para sobreescribir las propiedades de generación del formulario de edición
        /// </summary>
        /// <param name="sender">Objeto que se encuentra generando dinamicamente el formulario</param>
        /// <param name="e">Atributo que se encuentra en generación</param>
        public virtual void PersonalizarEditor(object sender, DataFormAutoGeneratingFieldEventArgs e)
        {
            switch (Modo)
            {
                case ModoEntidad.Nuevo:
                    e.Field.Mode = DataFieldMode.AddNew;
                    break;
                case ModoEntidad.SoloLectura:
                    e.Field.Mode = DataFieldMode.ReadOnly;
                    break;
                case ModoEntidad.Editar:
                    e.Field.Mode = DataFieldMode.Edit;
                    break;
            }
        }

        /// <summary>
        /// Método para detallar la accion de la accion de detalle
        /// </summary>
        /// <param name="parametro">Objeto mediante el cual el usuario validara la acción</param>
        public virtual void AplicarAccionDetalle(object parametro)
        {
        }

        /// <summary>
        /// Método para la implementación de la validacion de los detalles<br />
        /// El usuario debera sobreescribir completamente el método
        /// </summary>
        /// <returns>
        /// Un dato binario que se indica si se cumple o no con la validación de los detalles
        /// </returns>
        public virtual bool ValidarItems()
        {
            return true;
        }

        #endregion
    }
}